﻿#region License
/*
Copyright (c) 2009, G.W. van der Vegt
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided 
that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the 
  following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 
  the following disclaimer in the documentation and/or other materials provided with the distribution.

* Neither the name of G.W. van der Vegt nor the names of its contributors may be 
  used to endorse or promote products derived from this software without specific prior written 
  permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 
THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
*/
#endregion License

namespace Swiss
{
    using System;
    using System.Collections;
    using System.Collections.Specialized;
    using System.Diagnostics;
    using System.Runtime.InteropServices;

    /// <summary>
    /// 
    /// </summary>
    public class StoragePath : IEnumerable, IDisposable
    {
        private ArrayList fElements = new ArrayList();

        /// <summary>
        /// 
        /// </summary>
        public class StorageDir : IDisposable
        {
            /// <summary>
            /// Constructor.
            /// </summary>
            /// <param name="aStorageDir"></param>
            /// <param name="aStorage"></param>
            public StorageDir(string aStorageDir, Interop.IStorage aStorage)
            {
                fStorageDir = aStorageDir;
                fStorage = aStorage;
            }

            /// <summary>
            /// 
            /// </summary>
            public Interop.IStorage Storage
            {
                get
                {
                    return fStorage;
                }
                set
                {
                    fStorage = value;
                }
            }
            Interop.IStorage fStorage;

            /// <summary>
            /// 
            /// </summary>
            public String Path
            {
                get
                {
                    return fStorageDir;
                }
            }
            String fStorageDir;

            /// <summary>
            /// 
            /// </summary>
            public Int32 RefCount
            {
                get
                {
                    if (fStorage != null)
                    {
                        return Marshal.Release(Marshal.GetIUnknownForObject(fStorage)) - 1; //Returns oldreference Count so -1!
                    }
                    else
                    {
                        return 0;
                    }
                }
            }

            /// <summary>
            /// 
            /// </summary>
            public void Commit()
            {
                Commit(Interop.STGC.DEFAULT);
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="grfCommitFlags"></param>
            public void Commit(Interop.STGC grfCommitFlags)
            {
                Debug.WriteLine("StorageDir.Commit() for " + Path);
                fStorage.Commit((Int32)grfCommitFlags);
            }

            /// <summary>
            /// 
            /// </summary>
            public void Dispose()
            {
                Debug.WriteLine("StorageDir.Dispose() for " + Path);

                if (fStorage != null)
                {
                    Marshal.ReleaseComObject(fStorage);
                    fStorage = null;
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public static Char PathDelimiter
        {
            get
            {
                return System.IO.Path.DirectorySeparatorChar;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public String Path
        {
            get
            {
                string Result = "";
                IEnumerator fEnum = GetEnumerator();

                fEnum.Reset();
                while (fEnum.MoveNext())
                {
                    if (Result.Length == 0)
                    {
                        Result = ((StorageDir)fEnum.Current).Path;
                    }
                    else
                    {
                        Result = Result + PathDelimiter + ((StorageDir)fEnum.Current).Path;
                    }
                }
                return PathDelimiter + Result;
            }
        }

        private string fPath = "";
        private Interop.IStorage fRoot = null;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="aStreamName"></param>
        /// <param name="aStorage"></param>
        public StoragePath(string aStreamName, Interop.IStorage aStorage)
        {
            Debug.WriteLine("StorageDir() for " + aStreamName);

            fPath = aStreamName;
            if (!aStreamName.EndsWith("\\"))
            {
                if (aStreamName.Contains("."))
                {
                    fPath = ExtractFilePath(aStreamName);
                }
            }

            fRoot = aStorage;

            Interop.IStorage fStore = fRoot;

            if (fPath.Length != 0 && !fPath.Equals(PathDelimiter))
            {
                string s = fPath;

                if (s.StartsWith(PathDelimiter.ToString()))
                {
                    s = s.Substring(1);
                }

                while (s.Contains(PathDelimiter.ToString()))
                {
                    string sub = s.Substring(0, s.IndexOf(PathDelimiter)); //was -1
                    s = s.Substring(s.IndexOf(PathDelimiter) + 1);

                    fStore = AddStorage(fStore, sub);
                }

                if (s.Length != 0)
                {
                    AddStorage(fStore, s);
                }
            }

            fStore = null;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="aStorage"></param>
        /// <param name="aStorageDir"></param>
        /// <returns></returns>
        private Interop.IStorage AddStorage(Interop.IStorage aStorage, string aStorageDir)
        {
            Interop.IStorage aSubStore = null;

            if (StorageExists(aStorageDir))
            {
                fRoot.OpenStorage(
                    aStorageDir,
                    null,
                    Interop.STGM.TRANSACTED | Interop.STGM.READWRITE | Interop.STGM.SHARE_EXCLUSIVE,
                    IntPtr.Zero,
                    0,
                    out aSubStore);
            }
            else
            {
                fRoot.CreateStorage(
                    aStorageDir,
                    Interop.STGM.TRANSACTED | Interop.STGM.CREATE | Interop.STGM.READWRITE | Interop.STGM.SHARE_EXCLUSIVE,
                    0,
                    0,
                    out aSubStore);
            }

            Add(aStorageDir, aSubStore);

            //aStorage = aSubStore;

            return aSubStore;//was aStorage
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="aStreamName"></param>
        /// <returns></returns>
        public static String ExtractFileName(String aStreamName)
        {
            Int32 aPathLen = aStreamName.LastIndexOf(PathDelimiter);
            return aStreamName.Substring(aPathLen + 1, aStreamName.Length - aPathLen - 1);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="aStreamName"></param>
        /// <returns></returns>
        public static String ExtractFilePath(String aStreamName)
        {
            Int32 aPathLen = aStreamName.LastIndexOf(PathDelimiter);
            if (aPathLen != -1)
            {
                return aStreamName.Substring(0, aPathLen);
            }
            return "";
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="aPath"></param>
        /// <param name="aStorage"></param>
        /// <returns></returns>
        public Int32 Add(String aPath, Interop.IStorage aStorage)
        {
            return fElements.Add(new StorageDir(aPath, aStorage));
        }

        /// <summary>
        /// IEnumerable Interface Implementation: 
        /// Declaration of the GetEnumerator() method 
        /// required by IEnumerable
        /// </summary>
        /// <returns></returns>
        public IEnumerator GetEnumerator()
        {
            return new StoragePathEnumerator(this);
        }

        /// <summary>
        /// Get Current Storage.
        /// </summary>
        /// <returns></returns>
        public Interop.IStorage GetCD()
        {
            if (fElements.Count == 0)
            {
                return fRoot;
            }
            else
            {
                return ((StorageDir)(fElements[fElements.Count - 1])).Storage;
            }
        }

        /// <summary>
        /// Helper to Mimic Delphi's Assigned function.
        /// </summary>
        /// <param name="AObject"></param>
        /// <returns></returns>
        private bool Assigned(object AObject)
        {
            return (AObject != null);
        }

        /// <summary>
        /// Test if a Storage Exists in the (Sub)Storage.
        /// </summary>
        /// <param name="aStorageName"></param>
        /// <returns></returns>
        public Boolean StorageExists(String aStorageName)
        {
            Interop.IEnumSTATSTG fEnum;
            uint celtFetched;
            System.Runtime.InteropServices.ComTypes.STATSTG[] statstg = new System.Runtime.InteropServices.ComTypes.STATSTG[1];

            GetCD().EnumElements(0, (IntPtr)0, 0, out fEnum);
            while (fEnum.Next(1, statstg, out celtFetched) == 0)
            {
                if ((statstg[0].type == (Int32)Interop.STGTY.STORAGE) &&
                    String.Equals(aStorageName, statstg[0].pwcsName))
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Get a List of Items found in the Current Storage.
        /// </summary>
        /// <param name="filter">Look only for this kind of Storage Type</param>
        /// <returns>A StringCollection with the (file)names of Items found.</returns>
        public StringCollection StorageListing(Interop.STGTY filter)
        {
            StringCollection Result = new StringCollection();

            Interop.IEnumSTATSTG fEnum;
            uint celtFetched;
            System.Runtime.InteropServices.ComTypes.STATSTG[] statstg = new System.Runtime.InteropServices.ComTypes.STATSTG[1];

            GetCD().EnumElements(0, (IntPtr)0, 0, out fEnum);
            while (fEnum.Next(1, statstg, out celtFetched) == 0)
            {
                if ((statstg[0].type == (Int32)filter))
                {
                    Result.Add(statstg[0].pwcsName);
                }
            }

            return Result;
        }

        /// <summary>
        /// Test if a Stream Exists in the (Sub)Storage.
        /// </summary>
        /// <param name="aStreamName"></param>
        /// <returns></returns>
        public Boolean StreamExists(String aStreamName)
        {
            Interop.IEnumSTATSTG fEnum;
            uint celtFetched;
            System.Runtime.InteropServices.ComTypes.STATSTG[] statstg = new System.Runtime.InteropServices.ComTypes.STATSTG[1];

            GetCD().EnumElements(0, (IntPtr)0, 0, out fEnum);
            while (fEnum.Next(1, statstg, out celtFetched) == 0)
            {
                if ((statstg[0].type == (Int32)Interop.STGTY.STREAM) &&
                    String.Equals(aStreamName, statstg[0].pwcsName))
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// 
        /// </summary>
        public void Commit()
        {
            Commit(Interop.STGC.DEFAULT);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="grfCommitFlags"></param>
        public void Commit(Interop.STGC grfCommitFlags)
        {
            Debug.WriteLine("StoragePath.Commit() for " + this.Path);

            IEnumerator fEnum = GetEnumerator();

            fEnum.Reset();
            while (fEnum.MoveNext())
            {
                ((StorageDir)fEnum.Current).Commit(grfCommitFlags);
            }

            fRoot.Commit((Int32)grfCommitFlags);
        }

        /// <summary>
        /// 
        /// </summary>
        public void Dispose()
        {
            Debug.WriteLine("StoragePath.Dispose() for " + this.Path);

            //Note: Enumerator does not work....

            IEnumerator fEnum = GetEnumerator();

            fEnum.Reset();
            while (fEnum.MoveNext())
            {
                //Marshal.ReleaseComObject(((StorageDir)fEnum.Current).Storage);
                Debug.WriteLine("Dir Reference Count: " + ((StorageDir)fEnum.Current).RefCount.ToString() + " for " + this.Path); //is 2, where is the other comming from?

                ((StorageDir)fEnum.Current).Dispose();
            }
            /*

            for (Int32 i = 0; i < fElements.Count; i++)
            {
                //<MarshalAs(Unmanage­dType.Interface)> attribute to the ppObjectOpen
                Marshal.ReleaseComObject(((StorageDir)fElements[i]).Storage);
                ((StorageDir)fElements[i]).Storage = null;
            }
            */
            fRoot = null;

            GC.Collect();
        }

        #region Enumerator

        /// <summary>
        /// Inner class implements IEnumerator interface.
        /// </summary>
        private class StoragePathEnumerator : IEnumerator
        {
            private Int32 position = -1;
            private StoragePath s;

            /// <summary>
            /// 
            /// </summary>
            /// <param name="s"></param>
            public StoragePathEnumerator(StoragePath s)
            {
                this.s = s;
            }

            /// <summary>
            /// Declare the MoveNext method required by IEnumerator.
            /// </summary>
            /// <returns></returns>
            public bool MoveNext()
            {
                if (position < s.fElements.Count - 1)
                {
                    position++;
                    return true;
                }
                else
                {
                    return false;
                }
            }

            /// <summary>
            /// Declare the Reset method required by IEnumerator.
            /// </summary>
            public void Reset()
            {
                position = -1;
            }

            /// <summary>
            /// Declare the Current property required by IEnumerator.
            /// </summary>
            public Object Current
            {
                get
                {
                    return s.fElements[position];
                }
            }
        }
        #endregion Enumerator

        #region IDisposable Members

        /// <summary>
        /// 
        /// </summary>
        void IDisposable.Dispose()
        {
            //Call normal Dispose().
            Dispose();
        }

        #endregion
    }
}
